package pCloud.service;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;

import pCloud.message.DatabaseReply;
import pCloud.message.DatabaseRequest;
import pCloud.message.Message;
import pCloud.message.NumberMessage;
import pCloud.message.PolicyFile;
import pCloud.message.PolicyManagementReply;
import pCloud.message.PolicyManagementRequest;
import pCloud.message.PolicyQueryReply;
import pCloud.message.ReplyMessage;
import pCloud.message.RequestFile;
import pCloud.server.DatabaseHandle;
import pCloud.transport.SocketTransportHandle;

public class DataServerProxyServerProtHandle extends ProtocolHandle {

	AccessControlHandle accessControlHandler = null;
	DatabaseHandle databaseHandler = null;
	int policyCounter;
	int requestCounter;

	public void initializeAccessControlHandler(AccessControlHandle ach) {
		this.accessControlHandler = ach;
		this.policyCounter = 0;
		this.requestCounter = 0;
	}

	public void initializeDatabaseHandler(DatabaseHandle dbh) {
		this.databaseHandler = dbh;
	}

	@Override
	public void receive(Object o) throws Exception {
		this.handleProxyServerMsg((Message) o);
	}

	@Override
	public void send(Object m) throws Exception {
		this.conn.send(m);

	}

	private void handleProxyServerMsg(Message msg) throws Exception {
		if (msg.getMessageType() != Message.PROXY_TO_DATA_MSG) {
			System.err.println("PROXY_TO_DATA message expected, got "
					+ msg.getMessageType());
			return;
		}
		this.communicator.getLogger().debug(
				"PROXY_TO_DATA msg received from " + msg.getFrom());

		// 1. Unwrapp the message
		Message unwrappedMsg = (Message) msg.getMessageContent();
		if (unwrappedMsg.getMessageType() != Message.CLIENT_TO_PROXY_MSG)
			return;

		InetSocketAddress localSock = (InetSocketAddress) this.communicator
				.getConnection(msg.getFrom()).getLocalAddress();

		Message wrappedMsg = null;
		if (unwrappedMsg instanceof NumberMessage)
			wrappedMsg = this.processNumberMessage(
					(NumberMessage) unwrappedMsg, localSock, msg.getFrom());
		else if (unwrappedMsg.getMessageContent() instanceof DatabaseRequest)
			wrappedMsg = this.processDatabaseQuery(null,
					(DatabaseRequest) unwrappedMsg.getMessageContent(),
					localSock, unwrappedMsg.getFrom(), msg.getFrom());
		else if (unwrappedMsg.getMessageContent() instanceof PolicyManagementRequest) {
			PolicyManagementRequest pmr = (PolicyManagementRequest) unwrappedMsg
					.getMessageContent();
			if (pmr.getType() == PolicyManagementRequest.ADD)
				wrappedMsg = this.processAddingPolicy(
						(PolicyFile) pmr.getContent(), localSock,
						unwrappedMsg.getFrom(), msg.getFrom());
			else if (pmr.getType() == PolicyManagementRequest.REMOVE)
				wrappedMsg = this.processRemovingPolicy(
						(Integer) pmr.getContent(), localSock,
						unwrappedMsg.getFrom(), msg.getFrom());
			else
				// QUERY
				wrappedMsg = this.processPolicyQuerying(localSock,
						unwrappedMsg.getFrom(), msg.getFrom());
		} else if (unwrappedMsg.getMessageContent() instanceof RequestFile) {
			wrappedMsg = this.evaluatingACRequest(
					(RequestFile) unwrappedMsg.getMessageContent(), localSock,
					unwrappedMsg.getFrom(), msg.getFrom());
		}

		this.communicator.send(wrappedMsg.getTo(), wrappedMsg);
	}

	private Message processNumberMessage(NumberMessage nm,
			InetSocketAddress from, InetSocketAddress dest) {
		int val = ((Integer) nm.getMessageContent()).intValue();

		NumberMessage replyMsg = new NumberMessage(from, nm.getFrom(), val * 2,
				Message.CLIENT_TO_PROXY_MSG);

		return new Message(from, dest, replyMsg, Message.DATA_TO_PROXY_MSG);
	}

	private Message processDatabaseQuery(String joiningColumn,
			DatabaseRequest request, InetSocketAddress from,
			InetSocketAddress dest, InetSocketAddress proxyAdd)
			throws Exception {
		Message reply = new Message(from, dest,
				this.databaseHandler.evaluateRequest(joiningColumn, request),
				Message.PROXY_TO_CLIEN_MSG);
		return new Message(from, proxyAdd, reply, Message.DATA_TO_PROXY_MSG);
	}

	private synchronized Message processAddingPolicy(PolicyFile request,
			InetSocketAddress from, InetSocketAddress dest,
			InetSocketAddress proxyAdd) throws IOException {

		File policy = request.reconstructFileFromContent(
				"policyFile_" + from.getPort(), this.policyCounter);
		String fileName = policy.getName();
		this.policyCounter++;

		int status;
		if (this.accessControlHandler.addPolicy(policy.getName()))
			status = PolicyManagementReply.SUCCESS;
		else
			status = PolicyManagementReply.FAIL;

		this.communicator.getLogger().debug(
				"Adding policy to file " + policy.getName() + ". Status = "
						+ status);
		Message reply = new Message(from, dest, new PolicyManagementReply(
				status, policyCounter - 1, PolicyManagementReply.ADDED),
				Message.PROXY_TO_CLIEN_MSG);

		return new Message(from, proxyAdd, reply, Message.DATA_TO_PROXY_MSG);
	}

	private Message processRemovingPolicy(int policyFileIndex,
			InetSocketAddress from, InetSocketAddress dest,
			InetSocketAddress proxyAdd) throws Exception {
		String fileName = "policyFile_" + from.getPort() + "_"
				+ policyFileIndex;

		int status;
		if (this.accessControlHandler.removePolicy(fileName))
			status = PolicyManagementReply.SUCCESS;
		else
			status = PolicyManagementReply.FAIL;

		this.communicator.getLogger().debug(
				"Removing policy with index " + policyFileIndex + ". Status = "
						+ status);
		Message reply = new Message(from, dest, new PolicyManagementReply(
				status, policyFileIndex, PolicyManagementReply.REMOVED),
				Message.PROXY_TO_CLIEN_MSG);

		return new Message(from, proxyAdd, reply, Message.DATA_TO_PROXY_MSG);
	}

	private Message processPolicyQuerying(InetSocketAddress from,
			InetSocketAddress dest, InetSocketAddress proxyAdd)
			throws Exception {
		PolicyQueryReply pqr = (PolicyQueryReply) this.accessControlHandler
				.queryPolicy();

		Message reply = new Message(from, dest, pqr, Message.PROXY_TO_CLIEN_MSG);

		return new Message(from, proxyAdd, reply, Message.DATA_TO_PROXY_MSG);
	}

	private Message evaluatingACRequest(RequestFile request,
			InetSocketAddress from, InetSocketAddress dest,
			InetSocketAddress proxyAdd) throws Exception {

		File requestFile = request.reconstructFileFromContent("requestFile_"
				+ from.getPort(), this.requestCounter);
		this.communicator.getLogger().debug(
				"Evaluating request from file " + requestFile.getName());
		requestCounter++;

		int requestId = request.getRequestId();
		String joiningCol = request.getJoiningColumn();
		System.out.println("Joining column, in DataServerProxyServer = "+request.getJoiningColumn());
		ReplyMessage reply = (ReplyMessage) this.accessControlHandler
				.evaluateRequest(requestId, joiningCol, requestFile.getName(),
						this.databaseHandler);

		this.communicator.getLogger().debug(
				"Evaluated request " + request + ". Decision = "
						+ reply.getDecision());

		Message replyMsg = new Message(from, dest, reply,
				Message.PROXY_TO_CLIEN_MSG);

		return new Message(from, proxyAdd, replyMsg, Message.DATA_TO_PROXY_MSG);

	}
}
